<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"chuangges.gitee.io","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"right","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":"mac"},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="浏览器存储、HTTP 缓存">
<meta property="og:type" content="article">
<meta property="og:title" content="Web 缓存机制">
<meta property="og:url" content="https://chuangges.gitee.io/2023/12/04/24-web%E7%BC%93%E5%AD%98/index.html">
<meta property="og:site_name" content="闯哥要去流浪了">
<meta property="og:description" content="浏览器存储、HTTP 缓存">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2023-12-04T03:13:26.000Z">
<meta property="article:modified_time" content="2024-01-05T03:43:14.525Z">
<meta property="article:author" content="chuangges">
<meta property="article:tag" content="前端技术栈">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="https://chuangges.gitee.io/2023/12/04/24-web%E7%BC%93%E5%AD%98/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Web 缓存机制 | 闯哥要去流浪了</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="闯哥要去流浪了" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">闯哥要去流浪了</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">愿你岁月可回首，且以深情共白头</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://chuangges.gitee.io/2023/12/04/24-web%E7%BC%93%E5%AD%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/chuang.png">
      <meta itemprop="name" content="chuangges">
      <meta itemprop="description" content="chuangge's blog.">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="闯哥要去流浪了">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Web 缓存机制
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2023-12-04 11:13:26" itemprop="dateCreated datePublished" datetime="2023-12-04T11:13:26+08:00">2023-12-04</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2024-01-05 11:43:14" itemprop="dateModified" datetime="2024-01-05T11:43:14+08:00">2024-01-05</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF%E6%A0%88/" itemprop="url" rel="index"><span itemprop="name">前端技术栈</span></a>
                </span>
            </span>

          <br>
            <span class="post-meta-item" title="本文字数">
              <span class="post-meta-item-icon">
                <i class="far fa-file-word"></i>
              </span>
                <span class="post-meta-item-text">本文字数：</span>
              <span>18k</span>
            </span>
            <span class="post-meta-item" title="阅读时长">
              <span class="post-meta-item-icon">
                <i class="far fa-clock"></i>
              </span>
                <span class="post-meta-item-text">阅读时长 &asymp;</span>
              <span>16 分钟</span>
            </span>
            <div class="post-description">浏览器存储、HTTP 缓存</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h1 id="一、缓存机制"><a href="#一、缓存机制" class="headerlink" title="一、缓存机制"></a>一、缓存机制</h1><blockquote>
<p>缓存是临时存储数据的缓冲区，本质是一个存储结构化数据的内存 Hash，淘汰策略可以是基于空间&#x2F;容量&#x2F;时间。主要用于提高页面加载速度、减少服务器负担、减少网络带宽消耗、支持离线使用等性能优化。</p>
</blockquote>
<h2 id="计算机结构"><a href="#计算机结构" class="headerlink" title="计算机结构"></a>计算机结构</h2><ul>
<li><strong>硬件结构</strong>：主机 &#x3D; CPU + 主存，内存 &#x3D; Cache + 主存。<ol>
<li>处理器：CPU 中央处理器 (控制单元 &#x2F; 算术逻辑单元 ALU &#x2F; 寄存器 Register &#x2F; 时钟 Clock &#x2F; 高速缓存 Cache 组成)、GPU 图形处理器、TPU 张量处理单元 (机器学习)。</li>
<li>存储器：主存 (可分为 随机存取RAM 只读ROM)、辅存 (包括 硬盘 U盘 SD卡)</li>
<li>输入设备：比如键盘、鼠标、扫描仪、摄像头。</li>
<li>输出设备：比如显示器、打印机、音箱。</li>
</ol>
</li>
<li><strong>存储结构</strong>：容量更大速度更快成本更低，CPU 按顺序读取数据。<ol>
<li>寄存器 Registers：暂存指令和地址等信息，包括计数器和累加器等。</li>
<li>高速缓存 Cache：为了加快 CPU 读取内存数据而通过静态随机存储器 SRAM 存储频繁访问的数据，不需要定时刷新充电即可保持但断电会丢失数据，可分为三级 L1-L3。</li>
<li>主存：基于动态随机存储器 DRAM 通过电流加载程序和数据，需要刷新并断电时失效。</li>
<li>硬盘缓存：在硬盘上预留一块空间并通过磁性材料存储长期数据，缓存大小和位置等需求可进行系统设置，Buffer 就是为了加快硬盘等设备将数据写入内存而设计的。</li>
<li>硬盘 Disk：存储长期数据。CPU 需要读写数据时，硬盘将数据写入内存后由 CPU 读取。根据数据读取方式可分为电磁感应的机械硬盘 HDD、闪存芯片的固态硬盘 SSD。</li>
</ol>
</li>
</ul>
<h2 id="数据存储"><a href="#数据存储" class="headerlink" title="数据存储"></a>数据存储</h2><ul>
<li>存储技术<ul>
<li>DAS 直连式：将存储设备通过 SCSI&#x2F;SAS&#x2F;USB 接口直接连接到一个服务器。</li>
<li>NAS 网络附加：通过网络协议 NFS&#x2F;SMB&#x2F;CIFS 连接到局域网或广域网，为多个计算机提供文件级别的共享，本质是将本地文件系统迁移到 IP 网络设备。</li>
<li>SAN 区域网络：通过光纤通道或以太网等高速网络技术，提供块级别的结构数据共享。</li>
</ul>
</li>
</ul>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://www.jianshu.com/p/1acdb389f715">https://www.jianshu.com/p/1acdb389f715</a></p>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://zhuanlan.zhihu.com/p/655616526">https://zhuanlan.zhihu.com/p/655616526</a></p>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://blog.csdn.net/qq_38290251/article/details/131675965">https://blog.csdn.net/qq_38290251/article/details/131675965</a></p>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://blog.csdn.net/z_xiao_qiang/article/details/131562063">https://blog.csdn.net/z_xiao_qiang/article/details/131562063</a></p>
<p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://www.jb51.net/javascript/303837rsg.htm#_label6">https://www.jb51.net/javascript/303837rsg.htm#_label6</a></p>
<ul>
<li>内存存储：将数据存储在浏览器内存，具有读写快、容量小等特点。</li>
<li>本地文件存储：可通过 FileSystem API、Blob 将数据写入文件。<br>内存存储、本地文件存储和网络存储</li>
</ul>
<p>1 一般的高并发的应用程序，都在web层采用了以上几种缓存，一般静态资源（图片，js，css）都会采用nginx反向代理+客户端缓存来实现。<br>2  对于门户网站，尤其是首页的新闻，一般都会缓存起来，可以通过反向代理也可以通过应用程序缓存实现方式<br>3 对于下载或者视频网站，由于数据传输比较大，直接采用浏览器本地缓存实现。</p>
<h2 id="浏览器资源"><a href="#浏览器资源" class="headerlink" title="浏览器资源"></a>浏览器资源</h2><blockquote>
<p>浏览器加载静态</p>
</blockquote>
<p>的缓存规则是根据 http header、html meta 定义的，它们分别通过过期机制的新鲜度、验证机制的校验值来决定使用缓存还是从服务器去获取最新版本。静态资源的存储位置可查看开发者工具的 Network–Size，资源加载时查找顺序依次如下：</p>
<ol>
<li><strong>线程缓存 Service Worker</strong>：浏览器网页请求时通过代理实现缓存的独立线程，可配置缓存策略并将已加载资源缓存到硬盘和内存，只在首次加载时缓存一次。</li>
<li><strong>内存缓存 Memory Cache</strong>：当前页面的脚本和图片等已抓取资源，占据了网页进程一定的内存空间，在页面关闭或刷新时随着进程关闭而释放，再次打开时 disk cache。</li>
<li><strong>硬盘缓存 Disk Cache</strong>：根据 HTTP Herder 对请求资源进行强制缓存和协商缓存。</li>
<li><strong>推送缓存 Push Cache</strong>：以上都未命中时 HTTP2 相同连接只会推送一次。</li>
</ol>
<h2 id><a href="#" class="headerlink" title></a></h2><ul>
<li>HTTP 缓存策略<ul>
<li>Expires：过期时间，浏览器加载资源时直接从硬盘读取而不需要 HTTP 请求。</li>
<li>Last-Modified&#x2F;If-Modified-Since：最后修改时间，无论资源是否已更新，至少执行一次 HTTPS 请求。</li>
<li>ETag&#x2F;If-None-Match：有效性，资源被修改时会改变，相对 Last- Modified 更精确。</li>
<li>Cache-Control：缓存控制。比如：no-cache, private, max-age&#x3D;3600。</li>
</ul>
</li>
</ul>
<h1 id="一、缓存类型"><a href="#一、缓存类型" class="headerlink" title="一、缓存类型"></a>一、缓存类型</h1><ul>
<li>服务器缓存：进程内&#x2F;分布式缓存由后端开发维护，其它交给运维等专职人员。<ul>
<li><strong>CDN 缓存</strong>：存放 HTML、CSS、JS 等静态资源。</li>
<li><strong>DNS 缓存</strong>：域名解析为 IP 地址时存储映射关系。</li>
<li><strong>反向代理缓存</strong>：动静分离，只缓存用户请求的静态资源。</li>
<li><strong>数据库缓存</strong>：Redis&#x2F;memcached 将查询数据放到内存。</li>
<li><strong>进程内缓存</strong>：缓存应用字典等常用数据。</li>
<li><strong>分布式缓存</strong>：缓存数据库中的热点数据。</li>
</ul>
</li>
<li>客户端缓存：浏览器缓存由前端开发维护，其它交给 Android&#x2F;IOS 开发。<ul>
<li><strong>APP 缓存</strong>：类似硬盘的内部存储，Android SD 卡则是扩展存储。</li>
<li><strong>浏览器缓存</strong>：主要包括 BFcache 缓存、HTTP 缓存、JS 本地缓存。</li>
</ul>
</li>
</ul>
<h2 id="CDN-缓存"><a href="#CDN-缓存" class="headerlink" title="CDN 缓存"></a>CDN 缓存</h2><blockquote>
<p>CDN 是由负责内容分发的中心节点和不同区域服务器群的边缘节点组成的分布式网络。CDN 服务商通过在各地部署大量缓存了源网站静态资源的服务器节点，在用户访问时根据所在线路与位置等信息提供了就近访问资源的功能，解决了跨地域跨运营商的网站性能问题，但类似物流系统部署多套静态存储服务的成本较高。</p>
</blockquote>
<ul>
<li>优点<ul>
<li><strong>本地 Cache 加速</strong>：提升访问速度，尤其含有大量图片和静态页面站点。</li>
<li><strong>跨运营商的网络加速</strong>：消除了运营商之间的互联影响，保证不同用户的访问。</li>
<li><strong>远程加速</strong>：根据 DNS 负载均衡技术自动选择响应最快的 Cache 服务器。</li>
<li><strong>带宽优化</strong>：远程用户访问时直接从自动生成的远程镜像服务器读取数据。</li>
<li><strong>集群抗攻击</strong>：广泛分布的 CDN 机制可有效预防黑客入侵并降低攻击影响。</li>
</ul>
</li>
<li>缺点<ul>
<li><strong>不适宜缓存动态资源</strong>：解决方案为动态资源建立多级缓存或准实时同步。</li>
<li><strong>存在数据的一致性问题</strong>：解决方案为设置缓存失效时间、设置资源版本号。</li>
</ul>
</li>
<li>CDN 加速服务<ul>
<li><strong>CNAME</strong>：CDN 服务商提供并用于将域名映射到新域名的别名记录，配置后生效。</li>
<li><strong>使用方法</strong>：选择 CDN 服务商、上传静态资源并配置 CDN 服务、配置 DNS 解析设置。</li>
<li><strong>实现原理</strong>：本地 DNS 通过 CNAME 将域名请求重定向到能够响应最快的 CDN 节点。</li>
</ul>
</li>
</ul>
<h2 id="DNS-缓存"><a href="#DNS-缓存" class="headerlink" title="DNS 缓存"></a>DNS 缓存</h2><blockquote>
<p>DNS 是包括本地&#x2F;根&#x2F;顶级&#x2F;权威域名服务器的分布式域名解析系统，用于用户请求时将域名解析为 IP，解析历史一般会被浏览器、OS 操作系统、DNS 本地服务器缓存。</p>
</blockquote>
<ul>
<li><strong>基本概念</strong><ul>
<li>IP：网络主机的唯一数字化标识，主要包括由运营商分配的公网地址、由路由器分配的内网地址。格式为 <code>网络号.主机号.掩码 192.168.1.1</code>。</li>
<li>URL：统一资源定位符，格式为 <code>协议://IP或域名[:端口号]/路径/文件名[参数=值][锚点] http://baidu.com:80/p/index.html?age=1#nav</code>。</li>
<li>域名：用于数据传输时标识主机电子方位的 IP 字符化别名，可通过阿里云等域名供应商购买。格式为 <code>主机名.机构名.网络名.顶级域名 www.hust.edu.cn</code>。</li>
</ul>
</li>
<li><strong>DNS 普通查询</strong><ol>
<li>查询浏览器缓存：查看位置为 chrome:&#x2F;&#x2F;net-internals&#x2F;#dns。</li>
<li>查询操作系统缓存：用户可设置 URL IP 映射关系的 Host 文件。</li>
<li>查询家庭路由器缓存：操作系统会将域名解析请求转发给家庭路由器。</li>
<li>递归查询本地 DNS 服务器：主机逐个查询模式，缓存没有则去请求另一台 Local。</li>
<li>迭代上级 DNS 服务器：本地服务器代替主机查询的查询模式。Local 请求 ROOT 获取顶级 DNS 地址，Local 请求顶级获取权威 DNS 地址，Local 请求权威就可以获取 IP。</li>
<li>本地域名服务器将 IP 地址返回给操作系统，系统缓存后将 IP 返回给浏览器。</li>
</ol>
</li>
<li><strong>CDN 加速查询</strong><ol>
<li>浏览器输入 cdn 网址后向 Local DNS 发送解析请求。</li>
<li>Local DNS 向授权 DNS 发起请求并收到 CNAME 记录。</li>
<li>Local DNS 向智能调度 DNS 请求分配最佳节点的 IP。</li>
<li>浏览器重定向 CDN IP 地址后直接访问站点服务器资源。</li>
</ol>
</li>
</ul>
<h2 id="反向代理缓存"><a href="#反向代理缓存" class="headerlink" title="反向代理缓存"></a>反向代理缓存</h2><blockquote>
<p>中间服务器接收用户请求后，如果有缓存就直接将缓存内容返回，没有则将请求转发给应用服务器并将响应结果的静态资源缓存。常用的缓存应用服务器有 Varnish、Ngnix、Squid。</p>
</blockquote>
<ul>
<li>Varnish、Squid 是专业的缓存服务，Ngnix 需要第三方模块支持。</li>
<li>Varnish 采用内存型缓存，避免了频繁在内存和磁盘中交换文件，性能较高。</li>
<li>Squid 适合于各种静态的文件缓存，一般在前端 HAProxy&#x2F;Ngnix 做负载均衡跑多个实例。</li>
<li>Nginx 采用第三方模块 ncache 缓冲，性能基本达到 Varnish，可作为反向代理实现缓存。</li>
</ul>
<h2 id="进程内缓存"><a href="#进程内缓存" class="headerlink" title="进程内缓存"></a>进程内缓存</h2><blockquote>
<p>部署在一台服务器的单个应用本地缓存，一般可将数据缓存到速度较快但空间小的内存、容量大的硬盘。优点是读取速度快、实现简单、不需要网络传输，但服务重启后失效、受限于内存大小的限制、无法跨进程共享数据。常见方案如下：</p>
</blockquote>
<ul>
<li><strong>HashMap&#x2F;ConcurrentHashMap&#x2F;LinkedHashMap</strong>：Java 基本实现，可存键值对到内存。</li>
<li><strong>Caffeine</strong>：提供了自动加载、过期时间、缓存大小限制等特性，在高并发场景性能较好。</li>
<li><strong>Guava Cache</strong>：支持过期时间、最大缓存条数等特性，可通过监听器、行为配置等扩展。</li>
</ul>
<h2 id="分布式缓存"><a href="#分布式缓存" class="headerlink" title="分布式缓存"></a>分布式缓存</h2><blockquote>
<p>主要用于将数据存储在多台服务器组成集群的分布式系统，通过网络传输实现缓存共享。优点是扩展性好、支持高并发、大容量，具体实现时一般将数据存放在。常见方案如下：</p>
</blockquote>
<ul>
<li><strong>Redis</strong>：支持多种数据结构，读写性能高，但要考虑数据全内存的成本。</li>
<li><strong>Memcached</strong>：只适用于基于内存的缓存框架，不支持数据持久化和容灾。</li>
<li><strong>Ehcache</strong>：支持本地和分布式两种部署方式，提供多种策略并可扩展到集群。</li>
</ul>
<h2 id="BFcache-缓存"><a href="#BFcache-缓存" class="headerlink" title="BFcache 缓存"></a>BFcache 缓存</h2><blockquote>
<p>浏览器提升历史页面加载速度默认采用的往返缓存，浏览器前进后退时通过内存缓存整个页面，生命周期有 beforeunload、visibilitychange、pagehide、resume、pageshow、visibilitychange。</p>
</blockquote>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 页面来自 bfcache 则刷新、阻止页面进入 bfcache</span></span><br><span class="line"> <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;pageshow&#x27;</span>, <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (e.<span class="property">persisted</span>) &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">location</span>.<span class="title function_">reload</span>()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;pagehide&#x27;</span>, <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> dom = <span class="variable language_">document</span>.<span class="property">body</span>;</span><br><span class="line">    dom.<span class="property">children</span>.<span class="title function_">remove</span>();</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        dom.<span class="title function_">appendChild</span>(<span class="string">&quot;&lt;script type=&#x27;text/javascript&#x27;&gt;&quot;</span> +</span><br><span class="line">        <span class="string">&quot;window.location.reload();&lt;\/script&gt;&quot;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>


<h1 id="二、HTTP-缓存"><a href="#二、HTTP-缓存" class="headerlink" title="二、HTTP 缓存"></a>二、HTTP 缓存</h1><p>通过设置 Header 实现<br>HTTP 协议支持多种缓存策略，通过在 HTTP 响应头中设置 Cache-Control&#x2F;Expires 等相关指令，可以让浏览器有效地缓存资源，提高响应速度。另外，ETag、Last-Modified 是两种验证缓存是否过期的机制。</p>
<p>强缓存、协商缓存、启发式缓存</p>
<p>可通过设置 HTTP header、HTML meta 实现强制缓存和协商缓存。<br>分别从新鲜度和校验值两个维度来规定浏览器是直接使用缓存中的数据，还是需要去源服务器获取更新的版本</p>
<p>浏览器缓存分两类,强制缓存和协商缓存<br>强制缓存:浏览器在加载资源时，根据http header判断它是否命中强缓存，如果命中，浏览器直接从自己的缓存中读取资源，不会发请求到服务器。比如某个css文件，如果浏览器在加载它所在的网页时，这个css文件的缓存配置命中了强缓存，浏览器就直接从缓存中加载这个css，连请求都不会发送到网页所在服务器.<br>协商缓存：当强缓存没有命中的时候，浏览器一定会发送一个请求到服务器，通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存，如果协商缓存命中，服务器会将这个请求返回（304），但是不会返回这个资源的数据，而是告诉客户端可以直接从缓存中加载这个资源，于是浏览器就又会从自己的缓存中去加载这个资源；若未命中请求，则将资源返回客户端，并更新本地缓存数据（200）。<br>强制缓存不发请求到服务器，协商缓存会发请求到服务器。<br>强制缓存:Expires、Cache-control<br>协商缓存:Last-Modified&#x2F;If-Modified-Since, Etag&#x2F;If-None-Match</p>
<table>
<thead>
<tr>
<th>用户操作</th>
<th>强制缓存</th>
<th>协商缓存</th>
</tr>
</thead>
<tbody><tr>
<td>回车&#x2F;跳转</td>
<td>有效</td>
<td>有效</td>
</tr>
<tr>
<td>前进后退</td>
<td>有效</td>
<td>有效</td>
</tr>
<tr>
<td>F5 刷新</td>
<td>无效</td>
<td>有效</td>
</tr>
<tr>
<td>强制刷新</td>
<td>无效</td>
<td>无效</td>
</tr>
</tbody></table>
<h1 id="三、JS-本地缓存"><a href="#三、JS-本地缓存" class="headerlink" title="三、JS 本地缓存"></a>三、JS 本地缓存</h1><ul>
<li><strong>Cookie</strong>：4kb、过期前数据有效并存于内存但过期后存到硬盘、默认同源共享但可通过 domain 实现跨域共享、每次请求时都会携带到 HTTP Header。</li>
<li><strong>LocalStorage</strong>：2.5–10MB、数据一直有效并存于文件系统或硬盘、同源共享。</li>
<li><strong>SessionStorage</strong>：2.5–10MB、数据存储在内存、页面关闭即自动清除、同源共享。</li>
<li><strong>IndexedDB</strong>：大于 250MB、可存储二进制数据、网页数据库 WebSql 的替代方案。</li>
<li><strong>PWA</strong>：支持应用安装、离线缓存、消息推送的渐进式网页应用、AppCache 替代方案。</li>
<li><strong>FileSystem API</strong>：虚拟的文件系统，二进制数据存储需求、编辑文件，已废弃。</li>
</ul>
<h2 id="IndexedDB"><a href="#IndexedDB" class="headerlink" title="IndexedDB"></a>IndexedDB</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">IndexedDB</span> &#123;   <span class="comment">// let db = new IndexedDB(&#x27;test&#x27;);</span></span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">databaseName, version = <span class="number">1</span></span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">databaseName</span> = databaseName;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">version</span> = version;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">db</span> = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">open</span>(<span class="params">tableName</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">tableName</span> = tableName</span><br><span class="line">            <span class="keyword">const</span> indexedDB = <span class="variable language_">window</span>.<span class="property">indexedDB</span> || <span class="variable language_">window</span>.<span class="property">webkitIndexedDB</span>;</span><br><span class="line">            <span class="keyword">const</span> req = indexedDB.<span class="title function_">open</span>(<span class="variable language_">this</span>.<span class="property">databaseName</span>, <span class="variable language_">this</span>.<span class="property">version</span>);</span><br><span class="line">            req.<span class="property">onerror</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`IndDB：<span class="subst">$&#123;<span class="variable language_">this</span>.databaseName&#125;</span> 打开失败`</span>));</span><br><span class="line">            &#125;;</span><br><span class="line">            req.<span class="property">onsuccess</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="variable language_">this</span>.<span class="property">db</span> = req.<span class="property">result</span>;</span><br><span class="line">                <span class="title function_">resolve</span>();</span><br><span class="line">            &#125;;</span><br><span class="line">            req.<span class="property">onupgradeneeded</span> = <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">const</span> db = event.<span class="property">target</span>.<span class="property">result</span>;</span><br><span class="line">                <span class="keyword">const</span> oStore = db.<span class="title function_">createObjectStore</span>(tableName, &#123;</span><br><span class="line">                    <span class="attr">keyPath</span>: <span class="string">&#x27;dbKey&#x27;</span></span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">close</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">db</span>) &#123; <span class="keyword">return</span> &#125;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">db</span>.<span class="title function_">close</span>();</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">db</span> = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">getObjectStore</span>(<span class="params">tableName</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> trans = <span class="variable language_">this</span>.<span class="property">db</span>.<span class="title function_">transaction</span>(tableName, <span class="string">&#x27;readwrite&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> trans.<span class="title function_">objectStore</span>(tableName);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">addData</span>(<span class="params">data,tableName= <span class="variable language_">this</span>.tableName</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> objectStore = <span class="variable language_">this</span>.<span class="title function_">getObjectStore</span>(tableName);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> request = objectStore.<span class="title function_">add</span>(data);</span><br><span class="line">            request.<span class="property">onerror</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`IndexedDB：<span class="subst">$&#123;tableName&#125;</span> 添加失败`</span>));</span><br><span class="line">            &#125;;</span><br><span class="line">            request.<span class="property">onsuccess</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">resolve</span>(request.<span class="property">result</span>);</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">updateData</span>(<span class="params">data,tableName= <span class="variable language_">this</span>.tableName</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> objectStore = <span class="variable language_">this</span>.<span class="title function_">getObjectStore</span>(tableName);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> request = objectStore.<span class="title function_">put</span>(data);</span><br><span class="line">            request.<span class="property">onerror</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`IndexedDB：<span class="subst">$&#123;tableName&#125;</span> 更新失败`</span>));</span><br><span class="line">            &#125;;</span><br><span class="line">            request.<span class="property">onsuccess</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">resolve</span>(request.<span class="property">result</span>);</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">deleteData</span>(<span class="params">tableName, dbKey</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> objectStore = <span class="variable language_">this</span>.<span class="title function_">getObjectStore</span>(tableName);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> request = objectStore.<span class="title function_">delete</span>(dbKey);</span><br><span class="line">            request.<span class="property">onerror</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`IndexedDB：<span class="subst">$&#123;tableName&#125;</span> 删除失败`</span>));</span><br><span class="line">            &#125;;</span><br><span class="line">            request.<span class="property">onsuccess</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">resolve</span>(request.<span class="property">result</span>);</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">getItem</span>(<span class="params">dbKey,tableName = <span class="variable language_">this</span>.tableName</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> objectStore = <span class="variable language_">this</span>.<span class="title function_">getObjectStore</span>(tableName);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> request = objectStore.<span class="title function_">get</span>(dbKey);</span><br><span class="line">            request.<span class="property">onerror</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`IndexedDB：<span class="subst">$&#123;tableName&#125;</span> 获取失败`</span>));</span><br><span class="line">            &#125;;</span><br><span class="line">            request.<span class="property">onsuccess</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">resolve</span>(request.<span class="property">result</span>);</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">getAllData</span>(<span class="params">tableName</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> objectStore = <span class="variable language_">this</span>.<span class="title function_">getObjectStore</span>(tableName);</span><br><span class="line">        <span class="keyword">const</span> result = [];</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> request = objectStore.<span class="title function_">openCursor</span>();</span><br><span class="line">            request.<span class="property">onerror</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`IndexedDB：<span class="subst">$&#123;tableName&#125;</span> 获取失败`</span>));</span><br><span class="line">            &#125;;</span><br><span class="line">            request.<span class="property">onsuccess</span> = <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">const</span> cursor = event.<span class="property">target</span>.<span class="property">result</span>;</span><br><span class="line">                <span class="keyword">if</span> (cursor) &#123;</span><br><span class="line">                    result.<span class="title function_">push</span>(cursor.<span class="property">value</span>);</span><br><span class="line">                    cursor.<span class="title function_">continue</span>();</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="title function_">resolve</span>(result);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">setItem</span>(<span class="params">dbKey, data, tableName = <span class="variable language_">this</span>.tableName</span>)&#123;</span><br><span class="line">        <span class="keyword">const</span> oldData = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">getItem</span>(dbKey, tableName)</span><br><span class="line">        <span class="keyword">if</span> (oldData) &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="title function_">updateData</span>(&#123;...data, <span class="string">&#x27;dbKey&#x27;</span>: dbKey&#125;, tableName)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="title function_">addData</span>(&#123;...data, <span class="string">&#x27;dbKey&#x27;</span>: dbKey&#125;, tableName)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="PWA"><a href="#PWA" class="headerlink" title="PWA"></a>PWA</h2><blockquote>
<p>Progressive Web Apps，它是结合了 NativeApp web 的最好体验。首次访问时，用户可以直接在浏览器中进行访问而不需要安装应用。随着时间的推移，当用户和应用逐渐建立了联系，它会变得越来越强大。它能够快速地加载，即使在弱网络环境下也能够推送相关消息，也可以像原生应用那样添加至主屏，能够有全屏浏览的体验。</p>
</blockquote>
<ul>
<li>主要特点<ul>
<li><strong>渐进增强</strong>：支持的新特性的浏览器获得更好的体验，不支持的保持原来的体验。</li>
<li><strong>离线访问</strong>：通过 service workers 可以在离线或者网速差的环境下工作。</li>
<li><strong>类原生应用</strong>：使用 app shell model 做到原生应用般的体验。</li>
<li><strong>可安装</strong>：允许用户保留对他们有用的应用在主屏幕上，不需要通过应用商店。</li>
<li><strong>容易分享</strong>：通过 URL 可以轻松分享应用。</li>
<li><strong>持续更新</strong>：受益于 service worker 的更新进程，应用能够始终保持更新。</li>
<li><strong>安全</strong>：通过 HTTPS 来提供服务来防止网络窥探，保证内容不被篡改。</li>
<li><strong>可搜索</strong>：得益于 W3C manifests 元数据和 service worker 的登记，让搜索引擎能够找到 web 应用。</li>
<li><strong>再次访问</strong>：通过消息推送等特性让用户再次访问变得容易。</li>
</ul>
</li>
<li>功能模块<ul>
<li><strong>Service Workers</strong>：增强 Web 能力，主要用于实现资源离线缓存和更新。</li>
<li><strong>Web App Manifest</strong>：使 Web 更像 Native，它以 JSON 的格式定义 Web 应用的相关配置（应用名称、图像链接、自定义特性、启动默认配置、全屏设置等）。</li>
<li><strong>App Shell</strong>：提升显示效率的应用外壳，它是应用的用户界面所需的最基本的 HTML、CSS、JS，首次加载后立刻被缓存下来，不需要每次使用时都被下载，而是只异步加载需要的数据，以实现 UI 保持本地化。</li>
</ul>
</li>
</ul>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一、将应用添加到主屏幕：通过 index.html 注入配置文件 manifest.json。</span></span><br><span class="line">&lt;head&gt;</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">title</span>&gt;</span>index 页面<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;manifest&quot;</span> <span class="attr">href</span>=<span class="string">&quot;/manifest.json&quot;</span> /&gt;</span></span></span><br><span class="line">&lt;/head&gt;</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;My PWA&quot;</span>,</span><br><span class="line">    <span class="string">&quot;short_name&quot;</span>: <span class="string">&quot;PWA Demo&quot;</span>,</span><br><span class="line">    <span class="string">&quot;description&quot;</span>: <span class="string">&quot;The app that helps you understand PWA&quot;</span>,</span><br><span class="line">    <span class="string">&quot;display&quot;</span>: <span class="string">&quot;standalone&quot;</span>,        <span class="comment">// 首选显示模式</span></span><br><span class="line">    <span class="string">&quot;start_url&quot;</span>: <span class="string">&quot;/&quot;</span>,               <span class="comment">// 应用启动的地址</span></span><br><span class="line">    <span class="string">&quot;theme_color&quot;</span>: <span class="string">&quot;#313131&quot;</span>,       <span class="comment">// 桌面图标的背景色</span></span><br><span class="line">    <span class="string">&quot;background_color&quot;</span>: <span class="string">&quot;#313131&quot;</span>,  <span class="comment">// 启动应用和加载内容的过渡</span></span><br><span class="line">    <span class="string">&quot;icons&quot;</span>: [                      <span class="comment">// 桌面图标数组</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">&quot;src&quot;</span>: <span class="string">&quot;icon/lowres.webp&quot;</span>,</span><br><span class="line">            <span class="string">&quot;sizes&quot;</span>: <span class="string">&quot;48x48&quot;</span>,</span><br><span class="line">            <span class="string">&quot;type&quot;</span>: <span class="string">&quot;image/webp&quot;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">&quot;src&quot;</span>: <span class="string">&quot;icon/lowres&quot;</span>,</span><br><span class="line">            <span class="string">&quot;sizes&quot;</span>: <span class="string">&quot;48x48&quot;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">&quot;src&quot;</span>: <span class="string">&quot;icon/hd_hi.ico&quot;</span>,</span><br><span class="line">            <span class="string">&quot;sizes&quot;</span>: <span class="string">&quot;72x72 96x96 128x128 256x256&quot;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">&quot;src&quot;</span>: <span class="string">&quot;icon/hd_hi.svg&quot;</span>,</span><br><span class="line">            <span class="string">&quot;sizes&quot;</span>: <span class="string">&quot;72x72&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 二、ServiceWorker 缓存：index.html 注册、service-worker.js 拦截器</span></span><br><span class="line">&lt;img src=<span class="string">&quot;/images/hello.png&quot;</span> /&gt;  </span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">async</span> <span class="attr">src</span>=<span class="string">&quot;/js/script.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span>   </span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">if</span> (<span class="string">&#x27;serviceWorker&#x27;</span> <span class="keyword">in</span> navigator) &#123;           </span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    navigator.<span class="property">serviceWorker</span>.<span class="title function_">register</span>(<span class="string">&#x27;/service-worker.js&#x27;</span>, &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="attr">scope</span>: <span class="string">&#x27;/&#x27;</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;).<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">reg</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;ServiceWorker success：&#x27;</span>, reg.<span class="property">scope</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;).<span class="title function_">catch</span>(<span class="keyword">function</span> (<span class="params">err</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;ServiceWorker failed: &#x27;</span>, err);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> cacheName = <span class="string">&#x27;helloWorld&#x27;</span>;                    <span class="comment">// 缓存名称  </span></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&#x27;install&#x27;</span>, <span class="function"><span class="params">event</span> =&gt;</span> &#123;      <span class="comment">// 浏览器安装注册时 </span></span><br><span class="line">    event.<span class="title function_">waitUntil</span>(             <span class="comment">// 安装成功之前执行预装逻辑，增加成功率</span></span><br><span class="line">        caches.<span class="title function_">open</span>(cacheName)                  </span><br><span class="line">        .<span class="title function_">then</span>(<span class="function"><span class="params">cache</span> =&gt;</span> cache.<span class="title function_">addAll</span>([<span class="string">&#x27;/js/s.js&#x27;</span>, <span class="string">&#x27;/images/h.png&#x27;</span>]))</span><br><span class="line">    );</span><br><span class="line">&#125;);</span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&#x27;fetch&#x27;</span>, <span class="function"><span class="params">event</span> =&gt;</span> &#123;</span><br><span class="line">    event.<span class="title function_">respondWith</span>(</span><br><span class="line">        caches.<span class="title function_">match</span>(event.<span class="property">request</span>)    <span class="comment">// 检查请求 URL 是否匹配缓存内容</span></span><br><span class="line">        .<span class="title function_">then</span>(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (response) &#123; <span class="keyword">return</span> response &#125;;</span><br><span class="line">            <span class="keyword">var</span> reqToCache = event.<span class="property">request</span>.<span class="title function_">clone</span>();       </span><br><span class="line">            <span class="keyword">return</span> <span class="title function_">fetch</span>(reqToCache).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (!res || res.<span class="property">status</span> !== <span class="number">200</span>) &#123;      </span><br><span class="line">                    <span class="keyword">return</span> res;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// req res 是一个流并且只能消耗一次，但是缓存和请求需要两次</span></span><br><span class="line">                <span class="keyword">var</span> resToCache = res.<span class="title function_">clone</span>();          </span><br><span class="line">                caches.<span class="title function_">open</span>(cacheName)                           </span><br><span class="line">                .<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">cache</span>) &#123;</span><br><span class="line">                    cache.<span class="title function_">put</span>(reqToCache, resToCache);  </span><br><span class="line">                &#125;);</span><br><span class="line">                <span class="keyword">return</span> res;             </span><br><span class="line">            &#125;)</span><br><span class="line">        &#125;)</span><br><span class="line">    )</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 三、ServiceWorker 实现消息推送</span></span><br><span class="line"><span class="comment">// 1、index.html：提示用户并获得订阅信息，然后将信息保存到服务器。</span></span><br><span class="line"><span class="comment">// &lt;link rel=&quot;manifest&quot; href=&quot;/manifest.json&quot;&gt;</span></span><br><span class="line"><span class="keyword">const</span> vapidPublicKey = <span class="string">&#x27;BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDF&#x27;</span> +</span><br><span class="line"><span class="string">&#x27;EGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY&#x27;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">BaseToUint</span>(<span class="params">base64Str</span>) &#123;                                  </span><br><span class="line">    <span class="keyword">const</span> padding = <span class="string">&#x27;=&#x27;</span>.<span class="title function_">repeat</span>((<span class="number">4</span> - base64Str.<span class="property">length</span> % <span class="number">4</span>) % <span class="number">4</span>);</span><br><span class="line">    <span class="keyword">const</span> base64 = (base64Str + padding).<span class="title function_">replace</span>(<span class="regexp">/\-/g</span>, <span class="string">&#x27;+&#x27;</span>)</span><br><span class="line">    .<span class="title function_">replace</span>(<span class="regexp">/_/g</span>, <span class="string">&#x27;/&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> rawData = <span class="variable language_">window</span>.<span class="title function_">atob</span>(base64);</span><br><span class="line">    <span class="keyword">const</span> outputArray = <span class="keyword">new</span> <span class="title class_">Uint8Array</span>(rawData.<span class="property">length</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; rawData.<span class="property">length</span>; ++i) &#123;</span><br><span class="line">        outputArray[i] = rawData.<span class="title function_">charCodeAt</span>(i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> outputArray;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getSecret</span>(<span class="params">key</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!key) &#123; <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span> &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">btoa</span>(<span class="title class_">String</span>.<span class="property">fromCharCode</span>.<span class="title function_">apply</span>(<span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">Uint8Array</span>(key)))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (<span class="string">&#x27;serviceWorker&#x27;</span> <span class="keyword">in</span> navigator) &#123;</span><br><span class="line">    navigator.<span class="property">serviceWorker</span>.<span class="title function_">register</span>(<span class="string">&#x27;service-worker.js&#x27;</span>)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function"><span class="params">swReg</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// swReg 是否已订阅过消息推送</span></span><br><span class="line">        swReg.<span class="property">pushManager</span>.<span class="title function_">getSubscription</span>().<span class="title function_">then</span>(<span class="function"><span class="params">sub</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (sub) &#123; <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(sub); &#125;</span><br><span class="line">            <span class="keyword">return</span> swReg.<span class="property">pushManager</span>.<span class="title function_">subscribe</span>(&#123;</span><br><span class="line">                <span class="attr">userVisibleOnly</span>: <span class="literal">true</span>,</span><br><span class="line">                <span class="attr">applicationServerKey</span>: <span class="title class_">BaseToUint</span>(vapidPublicKey)</span><br><span class="line">            &#125;)</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">sub</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!sub.<span class="property">getKey</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;getKey 方法未找到&#x27;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">let</span> rawKey = sub.<span class="title function_">getKey</span>(<span class="string">&#x27;p256dh&#x27;</span>);</span><br><span class="line">        <span class="keyword">let</span> rawAuthSecret = sub.<span class="title function_">getKey</span>(<span class="string">&#x27;auth&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">fetch</span>(<span class="string">&#x27;./register&#x27;</span>, &#123;</span><br><span class="line">            <span class="attr">method</span>: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">            <span class="attr">headers</span>: <span class="keyword">new</span> <span class="title class_">Headers</span>(&#123;<span class="string">&#x27;content-type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span>&#125;),</span><br><span class="line">            <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">                <span class="attr">endpoint</span>: sub.<span class="property">endpoint</span>,</span><br><span class="line">                <span class="attr">key</span>: <span class="title function_">getSecret</span>(rawKey),</span><br><span class="line">                <span class="attr">authSecret</span>: <span class="title function_">getSecret</span>(rawAuthSecret)</span><br><span class="line">            &#125;)</span><br><span class="line">        &#125;);</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;ServiceWorker registration failed: &#x27;</span>, err);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2、app.js：实现服务器发送消息给 ServiceWorker。</span></span><br><span class="line"><span class="keyword">const</span> webpush = <span class="built_in">require</span>(<span class="string">&#x27;web-push&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> bodyParser = <span class="built_in">require</span>(<span class="string">&#x27;body-parser&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line">webpush.<span class="title function_">setVapidDetails</span>(                             </span><br><span class="line">    <span class="string">&#x27;mailto:contact@163.com&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9&#x27;</span> + </span><br><span class="line">    <span class="string">&#x27;EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0&#x27;</span></span><br><span class="line">);</span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/register&#x27;</span>, <span class="keyword">function</span> (<span class="params">req, res</span>) &#123;           </span><br><span class="line">    <span class="keyword">const</span> &#123;endpoint, key, authSecret&#125; = req.<span class="property">body</span>;</span><br><span class="line">    <span class="title function_">saveRegistrationDetails</span>(endpoint, key, authSecret); </span><br><span class="line">    <span class="keyword">const</span> pushSubscription = &#123;                          </span><br><span class="line">        <span class="attr">endpoint</span>: endpoint,</span><br><span class="line">        <span class="attr">keys</span>: &#123; <span class="attr">auth</span>: authSecret, <span class="attr">p256dh</span>: key &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    webpush.<span class="title function_">sendNotification</span>(pushSubscription,          </span><br><span class="line">        <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">            <span class="attr">msg</span>: <span class="string">&#x27;Thank you&#x27;</span>,</span><br><span class="line">            <span class="attr">url</span>: <span class="string">&#x27;http://localhost:3100/&#x27;</span>,</span><br><span class="line">            <span class="attr">icon</span>: <span class="string">&#x27;https://example.com/images/home.png&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">    )</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function"><span class="params">result</span> =&gt;</span> res.<span class="title function_">sendStatus</span>(<span class="number">200</span>))</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Web push app listening on port 3100!&#x27;</span>)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3、service-worker.js：监听 push 事件并将通知详情推送给用户。</span></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&#x27;push&#x27;</span>, <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> payload = event.<span class="property">data</span> ? <span class="title class_">JSON</span>.<span class="title function_">parse</span>(event.<span class="property">data</span>.<span class="title function_">text</span>()) : <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    <span class="keyword">var</span> title = <span class="string">&#x27;Progressive Times&#x27;</span>;</span><br><span class="line">    event.<span class="title function_">waitUntil</span>(   <span class="comment">// 通过提供信息显示 Web 推送通知</span></span><br><span class="line">        self.<span class="property">registration</span>.<span class="title function_">showNotification</span>(title, &#123;</span><br><span class="line">            <span class="attr">body</span>: payload.<span class="property">msg</span>,</span><br><span class="line">            <span class="attr">url</span>: payload.<span class="property">url</span>,</span><br><span class="line">            <span class="attr">icon</span>: payload.<span class="property">icon</span></span><br><span class="line">        &#125;)</span><br><span class="line">    );</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 四、webpack 配置文件集成：plugins 添加 workbox 缓存方案插件</span></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">GenerateSW</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;workbox-webpack-plugin&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> id = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">GenerateSW</span>(&#123;</span><br><span class="line">    <span class="attr">clientsClaim</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">skipWaiting</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">maximumFileSizeToCacheInBytes</span>: <span class="number">5</span> * <span class="number">1024</span> * <span class="number">1024</span>,</span><br><span class="line">    <span class="attr">cacheId</span>: <span class="string">`<span class="subst">$&#123;id&#125;</span>-gsw`</span>,</span><br><span class="line">    <span class="attr">exclude</span>: [<span class="string">&#x27;service-worker.js&#x27;</span>],</span><br><span class="line">    <span class="comment">// mode: &#x27;development&#x27;,</span></span><br><span class="line">    <span class="attr">runtimeCaching</span>: [&#123;</span><br><span class="line">        <span class="attr">urlPattern</span>: <span class="regexp">/^https:\/\/test.ceshi.com\/static/</span>,</span><br><span class="line">        <span class="attr">handler</span>: <span class="string">&#x27;CacheFirst&#x27;</span>,   <span class="comment">// 缓存优先模式，无缓存用网络请求</span></span><br><span class="line">        <span class="attr">options</span>: &#123;</span><br><span class="line">            <span class="attr">cacheName</span>: <span class="string">`<span class="subst">$&#123;id&#125;</span>-test-cdn`</span>,</span><br><span class="line">            <span class="attr">cacheableResponse</span>: &#123; <span class="attr">statuses</span>: [<span class="number">0</span>, <span class="number">200</span>] &#125;,</span><br><span class="line">            <span class="attr">expiration</span>: &#123;</span><br><span class="line">                <span class="attr">maxEntries</span>: <span class="number">50</span>,</span><br><span class="line">                <span class="attr">maxAgeSeconds</span>: <span class="number">60</span> * <span class="number">60</span> * <span class="number">24</span> * <span class="number">30</span>,   <span class="comment">// 30 天</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 五、Chrome Devtools 调试：开发者工具、应用、缓存</span></span><br></pre></td></tr></table></figure>


<h1 id="三、缓存机制"><a href="#三、缓存机制" class="headerlink" title="三、缓存机制"></a>三、缓存机制</h1><h2 id="缓存问题"><a href="#缓存问题" class="headerlink" title="缓存问题"></a>缓存问题</h2><ul>
<li><strong>缓存雪崩</strong>：指缓存不可用或大量缓存超时时间相同，大量请求访问数据库导致雪崩。<ul>
<li>增加缓存系统可用性：部署高可用方案 Redis Cluster + 主从 + 哨兵。</li>
<li>采用多级缓存方案：进程内本地缓存作为一级缓存、分布式缓存作为二级缓存。</li>
<li>限流、降级、熔断方案：避免被流量打死，比如使用 Hystrix 熔断降级。</li>
<li>使用持久化数据恢复：系统重启后自动从磁盘上加载数据，快速恢复缓存数据。</li>
<li>其它辅助手段：监控缓存和弹性扩容、缓存的过期时间可取个随机值。</li>
</ul>
</li>
<li>多级缓存方案：通过减少未命中惩罚(缓存未命中时所需的额外时间) 来提高缓存性能。<ul>
<li>1、浏览器向客户端发起请求，CDN 有缓存则直接返回，否则访问反向代理服务器。</li>
<li>2、反向代理服务器如果有缓存则直接返回，无缓存或动态请求则访问应用服务器。</li>
<li>3、应用服务器访问进程内缓存，有缓存则返回，代理服务器应缓存静态数据。</li>
<li>4、进程内缓存无数据，则读取分布式缓存并返回应用服务器缓存数据到本地。</li>
<li>5、如果分布式缓存无数据，则应用程序读取数据库数据，并放入分布式缓存。</li>
</ul>
</li>
<li><strong>缓存穿透</strong>：查询数据在数据库和缓存不存在，请求过多时会增大数据库压力。<ul>
<li>缓存空值：返回 NULL 的依然缓存，抛出异常则不缓存。</li>
<li>过滤不可能存在的数据：可使用布隆过滤器这种数据结构。</li>
<li>设置可访问的白名单：访问用户不在名单里，则进行拦截。</li>
<li>实时监控：命中率开始急速降低时排查访问对象和访问数据。</li>
</ul>
</li>
<li><strong>缓存击穿</strong>：热点数据失效时大量请求直接访问数据库，避免手段如下。<ul>
<li>分布式锁：锁住热点数据的 key，避免大量线程同时访问同一个 key。</li>
<li>定时异步刷新：可对部分数据采取失效前自动刷新的策略，而不是到期淘汰。</li>
<li>预先设置热门数据：高峰之前监控缓存的热门数据并适时调整过期时间。</li>
</ul>
</li>
</ul>
<h2 id="浏览器机制"><a href="#浏览器机制" class="headerlink" title="浏览器机制"></a>浏览器机制</h2><ul>
<li>性能优化<br>频繁变动的资源，比如HTML，采用协商缓存<br>CSS、JS、图片等资源采用强缓存，避开启发式缓存，使用 contenthash 命名<br>缓存API响应，在相同请求再次发生时，可以直接从缓存中获取结果，而无需重新查询API<br>采用效率更高的br压缩算法，可以减小传输文件的大小，从而缩短加载时间<br>合并请求，如果把多个访问小文件的请求合并成一个大的请求，虽然传输的总资源还是一样，但是减少请求，也就意味着减少了重复发送的 HTTP 头部<br>延迟加载，对于不需要立即显示的资源（如图片或视频），可以使用延迟加载技术。这样，只有当用户滚动到这些资源时，它们才会开始加载<br>善用preload和prefetch，我们可以优化浏览器资源加载的顺序和时机<br>善用base64，有效命中memory cache<br>使用HTTP&#x2F;2，HTTP&#x2F;2协议提供了性能改进，如多路复用和服务器推送。启用HTTP&#x2F;2可以进一步提高网站性能</li>
</ul>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1、html 文件设置 meta</span></span><br><span class="line">&lt;meta http-equiv=<span class="string">&quot;Cache-Control&quot;</span> </span><br><span class="line">content=<span class="string">&quot;no-cache, must-revalidate&quot;</span> /&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;Pragma&quot;</span> <span class="attr">content</span>=<span class="string">&quot;no-cache&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;Expires&quot;</span> <span class="attr">content</span>=<span class="string">&quot;0&quot;</span> /&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2、引用文件添加版本号或时间戳参数</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">&quot;css/demo.css?v=202249&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> /&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3、网址后面添加随机数</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">location</span> = <span class="string">&#x27;index.html?_r=&#x27;</span> + <span class="title class_">Math</span>.<span class="title function_">random</span>();</span><br></pre></td></tr></table></figure>

































    </div>

    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;margin-top:20px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
    
        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>chuangges
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="https://chuangges.gitee.io/2023/12/04/24-web%E7%BC%93%E5%AD%98/" title="Web 缓存机制">https://chuangges.gitee.io/2023/12/04/24-web缓存/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/zh_CN" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>


      <footer class="post-footer">

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2023/09/04/23-next%E9%A6%96%E5%B1%8F%E6%B8%B2%E6%9F%93/" rel="prev" title="最强首屏渲染方案 NextJS">
      <i class="fa fa-chevron-left"></i> 最强首屏渲染方案 NextJS
    </a></div>
      <div class="post-nav-item"></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%80%E3%80%81%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6"><span class="nav-number">1.</span> <span class="nav-text">一、缓存机制</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%93%E6%9E%84"><span class="nav-number">1.1.</span> <span class="nav-text">计算机结构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8"><span class="nav-number">1.2.</span> <span class="nav-text">数据存储</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B5%8F%E8%A7%88%E5%99%A8%E8%B5%84%E6%BA%90"><span class="nav-number">1.3.</span> <span class="nav-text">浏览器资源</span></a></li><li class="nav-item nav-level-2"><a class="nav-link"><span class="nav-number">1.4.</span> <span class="nav-text"></span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%80%E3%80%81%E7%BC%93%E5%AD%98%E7%B1%BB%E5%9E%8B"><span class="nav-number">2.</span> <span class="nav-text">一、缓存类型</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#CDN-%E7%BC%93%E5%AD%98"><span class="nav-number">2.1.</span> <span class="nav-text">CDN 缓存</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#DNS-%E7%BC%93%E5%AD%98"><span class="nav-number">2.2.</span> <span class="nav-text">DNS 缓存</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E7%BC%93%E5%AD%98"><span class="nav-number">2.3.</span> <span class="nav-text">反向代理缓存</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%BF%9B%E7%A8%8B%E5%86%85%E7%BC%93%E5%AD%98"><span class="nav-number">2.4.</span> <span class="nav-text">进程内缓存</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F%E7%BC%93%E5%AD%98"><span class="nav-number">2.5.</span> <span class="nav-text">分布式缓存</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#BFcache-%E7%BC%93%E5%AD%98"><span class="nav-number">2.6.</span> <span class="nav-text">BFcache 缓存</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%BA%8C%E3%80%81HTTP-%E7%BC%93%E5%AD%98"><span class="nav-number">3.</span> <span class="nav-text">二、HTTP 缓存</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%89%E3%80%81JS-%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98"><span class="nav-number">4.</span> <span class="nav-text">三、JS 本地缓存</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#IndexedDB"><span class="nav-number">4.1.</span> <span class="nav-text">IndexedDB</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#PWA"><span class="nav-number">4.2.</span> <span class="nav-text">PWA</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%89%E3%80%81%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6"><span class="nav-number">5.</span> <span class="nav-text">三、缓存机制</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%93%E5%AD%98%E9%97%AE%E9%A2%98"><span class="nav-number">5.1.</span> <span class="nav-text">缓存问题</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B5%8F%E8%A7%88%E5%99%A8%E6%9C%BA%E5%88%B6"><span class="nav-number">5.2.</span> <span class="nav-text">浏览器机制</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="chuangges" src="" data-original="/images/chuang.png">
  <p class="site-author-name" itemprop="name">chuangges</p>
  <div class="site-description" itemprop="description">chuangge's blog.</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">39</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/chuangges" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;chuangges" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://stackoverflow.com/yourname" title="StackOverflow → https:&#x2F;&#x2F;stackoverflow.com&#x2F;yourname" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-stack-overflow fa-fw"></i>StackOverflow</a>
      </span>
  </div>


  <div class="links-of-blogroll motion-element">
    <div class="links-of-blogroll-title"><i class="fa fa-link fa-fw"></i>
      友情链接
    </div>
    <ul class="links-of-blogroll-list">
        <li class="links-of-blogroll-item">
          <a href="https://juejin.im/" title="https:&#x2F;&#x2F;juejin.im&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">掘金</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://www.nowcoder.com/" title="https:&#x2F;&#x2F;www.nowcoder.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">牛客网</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://www.docschina.org/" title="https:&#x2F;&#x2F;www.docschina.org&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">印记中文</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="http://tool.lu/" title="http:&#x2F;&#x2F;tool.lu&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">在线工具</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://tinypng.com/" title="https:&#x2F;&#x2F;tinypng.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">图片压缩</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://bigjpg.com/" title="https:&#x2F;&#x2F;bigjpg.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">图片放大</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://tool.chinaz.com/" title="https:&#x2F;&#x2F;tool.chinaz.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">站长工具</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://learn-anything.xyz/" title="https:&#x2F;&#x2F;learn-anything.xyz&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">Learn-Anything</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://stackoverflow.com/" title="https:&#x2F;&#x2F;stackoverflow.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">Stack-Overflow</a>
        </li>
    </ul>
  </div>

      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2024</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">chuangge</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-chart-area"></i>
    </span>
    <span title="站点总字数">1m</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-coffee"></i>
    </span>
    <span title="站点阅读时长">15:14</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="external nofollow noopener noreferrer" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="external nofollow noopener noreferrer" target="_blank">NexT.Pisces</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","model":{"jsonPath":"/live2dw/assets/tororo.model.json"},"display":{"position":"left","width":180,"height":360},"mobile":{"show":false},"react":{"opacityDefault":1,"opacityOnHover":1},"log":false,"tagMode":false});</script>
        <style>
            [bg-lazy] {
                background-image: none !important;
                background-color: #eee !important;
            }
        </style>
        <script>
            window.imageLazyLoadSetting = {
                isSPA: false,
                preloadRatio: 1,
                processImages: null,
            };
        </script><script>window.addEventListener("load",function(){var t=/\.(gif|jpg|jpeg|tiff|png)$/i,r=/^data:image\/[a-z]+;base64,/;Array.prototype.slice.call(document.querySelectorAll("img[data-original]")).forEach(function(a){var e=a.parentNode;"A"===e.tagName&&(e.href.match(t)||e.href.match(r))&&(e.href=a.dataset.original)})});</script><script>!function(e){e.imageLazyLoadSetting.processImages=t;var n=e.imageLazyLoadSetting.isSPA,i=e.imageLazyLoadSetting.preloadRatio||1,r=o();function o(){var t=Array.prototype.slice.call(document.querySelectorAll("img[data-original]")),e=Array.prototype.slice.call(document.querySelectorAll("[bg-lazy]"));return t.concat(e)}function t(){n&&(r=o());for(var t,a=0;a<r.length;a++)0<=(t=(t=r[a]).getBoundingClientRect()).bottom&&0<=t.left&&t.top<=(e.innerHeight*i||document.documentElement.clientHeight*i)&&function(){var t,e,n,i,o=r[a];e=function(){r=r.filter(function(t){return o!==t})},(t=o).hasAttribute("bg-lazy")?(t.removeAttribute("bg-lazy"),e&&e()):(n=new Image,i=t.getAttribute("data-original"),n.onload=function(){t.src=i,t.removeAttribute("data-original"),e&&e()},t.src!==i&&(n.src=i))}()}function a(){clearTimeout(t.tId),t.tId=setTimeout(t,500)}t(),document.addEventListener("scroll",a),e.addEventListener("resize",a),e.addEventListener("orientationchange",a)}(this);</script></body>
</html>
